package com.sec.android.app.myfiles.feature;

import android.app.Fragment;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;

import com.samsung.android.sdk.slinkcloud.CloudGatewaySignInUtils;
import com.sec.android.app.myfiles.feature.cloud.AbsCloudImp;
import com.sec.android.app.myfiles.feature.cloud.CursorObserver;
import com.sec.android.app.myfiles.feature.cloud.GoogleCloudImp;
import com.sec.android.app.myfiles.feature.cloud.SamsungCloudImp;
import com.sec.android.app.myfiles.info.AppConstants;
import com.sec.android.app.myfiles.info.AppFeatures;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.abstraction.FileRecord.CloudType;
import com.sec.android.app.myfiles.util.JUnitHandler;
import com.sec.android.app.myfiles.util.UiUtils;

import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;

/**
 * Created by jaebae on 2016-01-27.
 */
public class CloudMgr implements CursorObserver.OnCursorChanged {
    private enum CloudCmd {
        INIT
    }

    public static final int MYFILES_GOOGLEDRIVE_LOGIN = 1;
    public static final int MYFILES_SAMSUNGDRIVE_LOGIN = 2;

    private static CloudMgr mInstance;
    private Context mContext;

    private LinkedHashMap<CloudType, AbsCloudImp> mAbsCloudImpList = new LinkedHashMap<>();
    private HashMap<CloudType, Boolean> mSignInMap = new HashMap<>();
    private HashSet<CloudStateListener> mListeners = new HashSet<>();
    private HashSet<OnSignInStateChangeListener> mSignInStateChangeListeners = new HashSet<>();

    private HandlerThread mThread;
    private static final String THREAD_NAME = "CloudThread";

    private BgHandler mBgHandler;
    private Handler mUiHandler;

    public interface CloudStateListener {
        void onCloudInit();

        void onAccountChanged(CloudType cloudType);
    }

    public interface OnSignInStateChangeListener {
        void onSignInStateChanged(CloudType cloudType, boolean signedIn);
    }

    private HashMap<CloudType, CursorObserver> mAccountObserverList = new HashMap<>();

    public static CloudMgr getInstance(Context context) {
        synchronized (CloudMgr.class) {
            if (mInstance == null) {
                mInstance = new CloudMgr(context);
            }
        }
        return mInstance;
    }

    public String getStoragePath(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.getStoragePath() : null;
    }

    public boolean isAccountRetrieved(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.isAccountRetrieved() : false;
    }

    public boolean isSignedIn(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.isSignedIn() : false;
    }

    public int getDeviceId(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.getDeviceId() : AppConstants.UNDEFINED;
    }

    public CloudType getCloudType(int deviceId) {
        CloudType ret = CloudType.None;
        CloudType[] cloudTypes = CloudType.values();
        if (cloudTypes != null) {
            for (CloudType type : cloudTypes) {
                AbsCloudImp imp = getCloudImp(type);
                if (imp != null && imp.getDeviceId() == deviceId) {
                    ret = type;
                    break;
                }
            }
        }
        return ret;
    }

    public void login(Fragment resultFragment, CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        if (cloudImp != null) {
            cloudImp.login(resultFragment);
        }
    }

    public String getUserId(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.getUserId() : null;
    }

    public void setSize(CloudType cloudType, long totalSize, long usedSize) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        if (cloudImp != null) {
            cloudImp.setTotalSize(totalSize);
            cloudImp.setUsedSize(usedSize);
        }
    }

    public long getTotalSize(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.getTotalSize() : AppConstants.UNDEFINED;
    }

    public long getUsedSize(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.getUsedSize() : AppConstants.UNDEFINED;
    }

    public String getAccountType(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.getAccountType() : null;
    }

    private void createCloudImp(Context context) {
        if (!AppFeatures.NO_SUPPORT_CLOUD) {
            if (AppFeatures.isSupportSamsungDrive()) {
                mAbsCloudImpList.put(CloudType.SamsungDrive, new SamsungCloudImp(context));
            }
            mAbsCloudImpList.put(CloudType.GoogleDrive, new GoogleCloudImp(context));
        }
    }

    private CloudMgr(Context context) {
        mContext = context.getApplicationContext();
        if (UiUtils.supportCloudGateway(mContext)) {
            createCloudImp(mContext);
            createThread();

            initCmd();
        }
    }

    public LinkedHashMap<CloudType, AbsCloudImp> getCloudImpList() {
        return mAbsCloudImpList;
    }

    private synchronized void createThread() {
        if (mThread == null) {
            mThread = new HandlerThread(THREAD_NAME, Process.THREAD_PRIORITY_BACKGROUND);
            mThread.start();

            mBgHandler = new BgHandler(mThread.getLooper());
            mUiHandler = new WeakRefHandler(this);
        } else {
            Log.d(this, "createThread : mThread was null but created already");
        }
    }


    public void addListener(CloudStateListener callback) {
        mListeners.add(callback);
        registerObserver();
    }

    public void removeListener(CloudStateListener callback) {
        mListeners.remove(callback);
        stopObserver();
    }

    public void addSignInStateChangeListener(OnSignInStateChangeListener l) {
        mSignInStateChangeListeners.add(l);
    }

    public void removeSignInStateChangeListener(OnSignInStateChangeListener l) {
        mSignInStateChangeListeners.remove(l);
    }

    public void initCmd() {
        mBgHandler.sendMessage(mBgHandler.obtainMessage(0, new Object[]{CloudCmd.INIT}));
    }

    public static void keepCloudServiceAlive(Context context, boolean alive) {
        if (UiUtils.isCloudGatewayEnabled(context)) {
            if (alive) {
                CloudGatewaySignInUtils.getInstance(context).checkProviderInit();
            }
            CloudGatewaySignInUtils.getInstance(context).keepServiceAlive(alive);
        }
    }

    private Object[] init() {
        Object[] ret = new Object[]{CloudCmd.INIT};

        if (mAbsCloudImpList != null) {
            Collection<AbsCloudImp> cloudImpList = mAbsCloudImpList.values();
            for (AbsCloudImp cloudImp : cloudImpList) {
                cloudImp.retrieveAccountInfo();
            }
        }
        return ret;
    }

    public boolean retrieveAccountInfo(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.retrieveAccountInfo() : false;
    }

    private final class BgHandler extends JUnitHandler<Object[]> {
        public BgHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            Object[] params = getMsgValue(msg.obj);
            if (params != null) {
                CloudCmd cmd = (CloudCmd) params[0];
                Object[] ret = null;
                switch (cmd) {
                    case INIT:
                        ret = init();
                        break;
                }

                if (ret != null) {
                    mUiHandler.sendMessage(mUiHandler.obtainMessage(0, ret));
                }
            }
        }
    }

    public FileRecord getCloudHomeRecord(CloudType cloudType) {
        AbsCloudImp cloudImp = getCloudImp(cloudType);
        return (cloudImp != null) ? cloudImp.getCloudHomeRecord() : null;
    }

    private static class WeakRefHandler extends Handler {
        WeakReference<CloudMgr> mCloudMgr = null;

        WeakRefHandler(CloudMgr cloud) {
            super(Looper.getMainLooper());
            mCloudMgr = new WeakReference<>(cloud);
        }

        public void handleMessage(Message msg) {
            CloudMgr cloudMgr = mCloudMgr.get();
            if (cloudMgr == null) {
                return;
            }
            CloudType[] values = CloudType.values();
            for (CloudType cloudType : values) {
                cloudMgr.mSignInMap.put(cloudType, cloudMgr.isSignedIn(cloudType));
            }

            Object[] params = (Object[]) msg.obj;
            if (params != null) {
                CloudCmd cmd = (CloudCmd) params[0];

                for (CloudStateListener listener : cloudMgr.mListeners) {
                    switch (cmd) {
                        case INIT:
                            listener.onCloudInit();
                            break;
                    }
                }
            }
        }
    }

    private void registerObserver() {
        if (mAccountObserverList.isEmpty()) {
            Collection<AbsCloudImp> cloudImpList = mAbsCloudImpList.values();
            for (AbsCloudImp cloudImp : cloudImpList) {
                CursorObserver accountObserver = new CursorObserver(new Handler(), this);
                observerThread(accountObserver, cloudImp);
                mAccountObserverList.put(cloudImp.getCloudType(), accountObserver);
            }
        }
    }

    private void observerThread(final CursorObserver accountObserver, final AbsCloudImp cloudImp) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    accountObserver.start(cloudImp.getAccountInfoCursor());
                } catch (Exception ex) {
                    Log.d(this, ex.getMessage());
                }
            }
        }).start();
    }

    private void stopObserver() {
        if (mListeners.isEmpty()) {
            Collection<AbsCloudImp> cloudImpList = mAbsCloudImpList.values();
            for (AbsCloudImp cloudImp : cloudImpList) {
                mAccountObserverList.get(cloudImp.getCloudType()).stop();
                mAccountObserverList.remove(cloudImp.getCloudType());
            }
        }
    }

    @Override
    public void onCursorChanged(CursorObserver from) {
        Collection<AbsCloudImp> cloudImpList = mAbsCloudImpList.values();

        for (AbsCloudImp cloudImp : cloudImpList) {
            CloudType cloudType = cloudImp.getCloudType();
            CursorObserver accountObserver = mAccountObserverList.get(cloudType);
            if ((accountObserver != null && from != null) && (accountObserver.getId() == from.getId())) {
                cloudImp.retrieveAccountInfo();

                for (CloudStateListener listener : mListeners) {
                    listener.onAccountChanged(cloudType);
                }

                Boolean prevSignedIn = mSignInMap.get(cloudType);
                boolean currentSignedIn = cloudImp.isSignedIn();
                if (prevSignedIn == null || prevSignedIn != currentSignedIn) {
                    mSignInMap.put(cloudType, currentSignedIn);
                    for (OnSignInStateChangeListener l : mSignInStateChangeListeners) {
                        l.onSignInStateChanged(cloudType, currentSignedIn);
                    }
                }
            }
        }
    }

    public AbsCloudImp getCloudImp(CloudType cloudType) {
        return mAbsCloudImpList.get(cloudType);
    }
}
